﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading.Tasks;
using UMC.Net;

namespace UMC.Proxy
{

    public abstract class HttpMime : IDisposable
    {

        public abstract int Id { get; }
        public int ActiveTime
        {

            get; set;
        }
        public int TimeOut { protected set; get; } = 20;

        public virtual string Scheme => "http";


        public void OutputFinish()
        {

            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            this.TimeOut = 20;
            this.Request = null;

        }
        public abstract void Write(byte[] buffer, int offset, int count);


        public abstract void Dispose();
        public abstract String Host { get; }
        public abstract String RemoteIpAddress { get; }


        protected MimeRequest Request;

        protected void WebSocket(NetContext context)
        {
            if (context.Tag is NetHttpRequest)
            {
                var webr = context.Tag as NetHttpRequest;
                this.WebSocket(webr);
            }
            else
            {
                OutText(403, "WebSocket");
            }
        }

        void WebSocket(NetHttpRequest webRequest)
        {
            var url = webRequest.Address;
            webRequest.Headers["Connection"] = "Upgrade";

            var eventArgs = new ConnectAsyncEventArgs();
            eventArgs.Connected = async (client) =>
            {

                byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x500);
                try
                {
                    if (url.Scheme == "https")
                    {
                        SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
                        await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);

                        await ssl.WriteAsync(_data, 0, UMC.Net.NetHttpResponse.Header(webRequest, _data));
                        int size = await ssl.ReadAsync(_data, 0, _data.Length);

                        if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                        {
                            this.Request = new HttpsWebSocket(this, ssl);

                            this.Write(_data, 0, size);
                        }
                        else
                        {
                            this.Write(_data, 0, size);
                            this.Dispose();
                        }
                    }
                    else
                    {
                        client.Send(_data, 0, UMC.Net.NetHttpResponse.Header(webRequest, _data), SocketFlags.None);
                        var size = client.Receive(_data, 0, _data.Length, SocketFlags.None);
                        if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                        {
                            this.Request = new HttpWebSocket(this, client);
                            this.Write(_data, 0, size);
                        }
                        else
                        {

                            this.Write(_data, 0, size);
                            this.Dispose();
                        }

                    }
                }
                finally
                {
                    System.Buffers.ArrayPool<byte>.Shared.Return(_data);
                }
            };


            eventArgs.Error = (ex) =>
            {
                this.OutText(500, ex.ToString());

            };
            eventArgs.Start(0, url.Host, url.Port);


            HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
        }
        public virtual void PrepareRespone(HttpMimeRequest request)
        {

            this.TimeOut = 300;


            try
            {
                if (request.IsWebSocket)
                {
                    if (request.RawUrl.StartsWith("/UMC.WS/"))
                    {
                        var Device = request.Url.AbsolutePath.Length > 8 ? request.Url.AbsolutePath.Substring(8) : request.Cookies["device"];
                        if (String.IsNullOrEmpty(Device) == false)
                        {
                            string secWebSocketKey = request.Headers["Sec-WebSocket-Key"];
                            if (String.IsNullOrEmpty(secWebSocketKey) == false)
                            {
                                var buffers = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);

                                var webr = new UMC.Host.HttpWebSocket(this.Write, UMC.Data.Utility.Guid(Device, true).Value, this.Dispose);
                                this.Request = webr;

                                var size = secWebSocketKey.WriteBytes(buffers, 0);
                                size += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".WriteBytes(buffers, size);
                                int len = System.Security.Cryptography.SHA1.HashData(buffers.AsSpan(0, size), buffers.AsSpan(size, 24));
                                string secWebSocketAcceptString = Convert.ToBase64String(buffers.AsSpan(size, len));
                                var writer = new Net.TextWriter(request._context.Write, buffers);
                                writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
                                writer.Write("Connection: Upgrade\r\n");
                                writer.Write("Upgrade: websocket\r\n");
                                writer.Write($"Sec-WebSocket-Accept: {secWebSocketAcceptString}\r\n");
                                writer.Write("Server: ITME\r\n\r\n");
                                writer.Flush();
                                writer.Dispose();
                                System.Buffers.ArrayPool<byte>.Shared.Return(buffers);
                                HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
                                if (UMC.Host.HttpServier.LicenseUsersSession > 0)
                                {
                                    Data.Caches.ICacheSet cacheSet = UMC.Data.HotCache.Cache<UMC.Data.Entities.Session>();
                                    if (cacheSet.Count > UMC.Host.HttpServier.LicenseUsersSession)
                                    {
                                        webr.Send("{\"msg\":\"版本超限，请升级\"}");
                                    }
                                }
                            }
                            else
                            {
                                OutText(403, "not validate websocket headers");
                            }
                        }
                        else
                        {
                            OutText(403, "Permission denied");
                        }
                    }
                    else
                    {
                        var context = new HttpMimeContext(request, new HttpMimeResponse(this, request));
                        context.ProcessRequest();
                        this.WebSocket(context);
                    }
                }
                else
                {
                    var context = new HttpMimeContext(request, new HttpMimeResponse(this, request));
                    context.ProcessRequest();
                    context.ProcessAfter();
                }

            }
            catch (Exception ex)
            {
                OutText(500, "text/plain", ex.ToString());

            }


        }

        public void OutText(int status, string contentType, String text)
        {
            var writer = new TextWriter(this.Write);
            writer.Write($"HTTP/1.1 {status} {HttpStatusDescription.Get(status)}\r\n");
            writer.Write($"Content-Type: {contentType}; charset=utf-8\r\n");
            writer.Write($"Content-Length: {System.Text.Encoding.UTF8.GetByteCount(text)}\r\n");
            writer.Write("Connection: close\r\n");
            writer.Write("Server: Apiumc\r\n\r\n");
            writer.Write(text);
            writer.Flush();
            writer.Close();
            this.Dispose();
        }
        public void OutText(int status, String text)
        {
            this.OutText(status, "text/plain", text);

        }
    }
}

